package com.bzframework.pro.authorize.strategy.login;

import com.bzframework.pro.authorize.constants.RedisConstants;
import com.bzframework.pro.authorize.entity.UserEntity;
import com.bzframework.pro.authorize.service.UserService;
import com.bzframework.pro.authorize.token.SimpleUserToken;
import com.bzframework.pro.authorize.token.UserToken;
import com.bzframework.pro.authorize.token.generator.PasswordGenerator;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.hswebframework.web.exception.BusinessException;
import org.hswebframework.web.id.IDGenerator;
import org.springframework.data.redis.core.ReactiveRedisOperations;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author bingzhong
 * @Date 2024/12/13 15:12
 * @Description
 */
@LoginStrategyConfig(strategy = LoginTypeEnum.password)
@RequiredArgsConstructor
public class PasswordLoginStrategy implements LoginStrategy {

    private final ReactiveRedisOperations<String, String> redisOperations;

    private final PasswordGenerator passwordGenerator;

    private final UserService userService;

    private static final Duration tokenExpireTime = Duration.ofHours(2);

    @Override
    public Mono<UserToken> signIn(Map<String, Object> parameter) {

        String username = String.valueOf(parameter.getOrDefault("username", ""));
        String password = String.valueOf(parameter.getOrDefault("password", ""));

        if (StringUtils.isAnyEmpty(username, password)){
            return Mono.error(new RuntimeException("用户名或密码不能为空"));
        }

        String key = String.valueOf(parameter.getOrDefault("key",""));
        Assert.hasText(key, "captcha key is not null");

        String inputCaptcha = String.valueOf(parameter.getOrDefault("captcha",""));
        if (StringUtils.isEmpty(inputCaptcha)) {
            return Mono.error(new BusinessException("验证码不能为空"));
        }

        String captchaLoginKey = RedisConstants.getRedisKey(RedisConstants.CAPTCHA_LOGIN, key);
        return Mono.zip(
                redisOperations.opsForValue()
                               .get(captchaLoginKey)
                               .switchIfEmpty(Mono.error(new BusinessException("验证码已过期"))),
                userService.createQuery()
                           .and(UserEntity::getUsername, username)
                           .fetchOne()
                           .switchIfEmpty(Mono.error(new BusinessException("用户不存在")))
        ).flatMap(tuple2 -> {

            String captcha = tuple2.getT1();
            if (!inputCaptcha.equalsIgnoreCase(captcha)) {
                return Mono.error(new BusinessException("验证码错误"));
            }

            UserEntity userEntity = tuple2.getT2();
            String storagePassword = userEntity.getPassword();
            String encryptedPassword = passwordGenerator.generateEncryptedPassword(userEntity.getSalt(), password);
            if (!storagePassword.equals(encryptedPassword)) {
                return Mono.error(new BusinessException("密码错误"));
            }
            String role = userEntity.getRole().name();
            String token = IDGenerator.SNOW_FLAKE_STRING.generate();
            Map<String,String> tokenMap = new HashMap<>();
            tokenMap.put("userId", userEntity.getId());
            tokenMap.put("token", token);
            String signInTime = String.valueOf(System.currentTimeMillis());
            tokenMap.put("signInTime", signInTime);
            tokenMap.put("role", role);
            String tokenKey = RedisConstants.getRedisKey(RedisConstants.USER_TOKEN, token);

            return redisOperations.opsForHash()
                                  .putAll(tokenKey,tokenMap)
                                  .then(redisOperations.expire(tokenKey, tokenExpireTime))
                                  .then(redisOperations.delete(captchaLoginKey))
                                  .thenReturn(SimpleUserToken.of(userEntity.getId(), token, signInTime, role));
        });
    }

    @Override
    public Mono<Long> signOut(String token) {
        return redisOperations.delete(RedisConstants.getRedisKey(RedisConstants.USER_TOKEN, token));
    }
}
